/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/* 
 * File:   KKSFile.h
 * Author: ever
 *
 * Created on 2018年5月9日, 上午11:03
 */

#ifndef KKSFILE_H
#define KKSFILE_H


#include "TL_Mmap.h"


/**
 *  |Head|Item Meta*n|Item Data*n|
 */

#include "config.h"
#include <set>
#include <map>
#include <functional>
#include <string>
#include <unistd.h>
#include <TL_Common.h>

using namespace tidp;
#define PAGE_SIZE 4096
#define KKS_FILE_MAGIC 0x0230

#define KKS_FILE_HEAD_MAX_SIZE ((1 << 17) * sizeof(KKSFile::KKS_Meta))
//单个文件支持的kks数量;  131072 - sizeof(KKS_File_Head)/
#define KKS_MAX_COUNT_PEER_FILE ((1 << 17)- sizeof(KKSFile::KKS_File_Head)/sizeof(KKSFile::KKS_Meta) - 1)
//单个记录块的大小，尽量减少空洞，存储4096个点值; pagesize 对齐
#define KSS_ROW_LENGTH (sizeof(KKSFile::KKS_Value) * PAGE_SIZE)
#define KKS_ROW_COUNT ((KSS_ROW_LENGTH - sizeof(KKSFile::KKS_ROW_Head))/sizeof(KKSFile::KKS_Value))
using std::string;

class KKSFile {
public:

    /**
     * 存储格式 : |KKS_File_Head|KKS_Meta*KKS_MAX_COUNT_PEER_FILE|data...
     */
    KKSFile() = default;

    KKSFile(const string &file);

    KKSFile(const KKSFile &orig) = delete;

    virtual ~KKSFile();

    struct __attribute__ ((packed)) KKS_File_Head {
        u16_t magic = KKS_FILE_MAGIC;
        u16_t major = 0;
        u16_t minor = 1;
        u16_t revision = 1;
        u32_t kks_max_count = KKS_MAX_COUNT_PEER_FILE;
        u32_t kks_count = 0; //真实点个数
        off_t offset_tail = KKS_FILE_HEAD_MAX_SIZE; //全局offset，未使用的
    };

    struct __attribute__ ((packed)) KKS_Meta {
        u32_t kks_id = 0;
        off_t offset_first = 0;
        off_t offset_current = 0; //当前正在使用offset，用于重启
        //u32_t start_tm = 0;
        /*
        off_t offset_am = 0;
        u16_t count_am = 0;
        u32_t start_tm_am = 0;
        off_t offset_pm = 0;
        u16_t count_pm = 0;
        u32_t start_pm = 0;
         */
        u8_t has_health = HAS_KKS_HEALTH; //是否有健康度
        u8_t switch_type = 0; //启停标示  0 1 2 表明该测点是否是启停标示的点
        char kks_type = 'A';
    };

    struct __attribute__ ((packed)) KKS_ROW_Head {
        off_t offset_next = 0;
        u32_t start_tm = 0; //当前ROW的start time
        u32_t tail_tm = 0; //最后一个时间
        u16_t count = 0; //当row含有值的个数，包含溢出值
        int delta = 0; //上一个值的delta
    };

    struct __attribute__ ((packed)) KKS_Value {

        /**
         * 关于delta_delta_tm的范围:
         * 最高位bit为1的时候，称为溢出值，表示这个差值超出了0x7F的范围，
         * 此时最高位为1，value=实际delta_delta_tm，下一个单位delta_delta_tm=0,value为实际值
         * 
         */
        u8_t delta_delta_tm;
        char quality;
        u32_t value;
    };

    struct __attribute__ ((packed)) KKS_Float_Value {
        u8_t delta_delta_tm;
        char quality;
        float value;
    };

    struct KKS_Meta_Value {
        KKS_ROW_Head *row_head = nullptr;
        //当前row，可用value，null情况下不可用，通过辅助函数进行迁移后移，使用时候并判断是否null
        KKS_Value *row_tail_value = nullptr;
        TL_Mmap memap;
        //vector<off_t> row_chains;
    };


    using kks_meta_set_type = std::map<KKS_Meta *,
            KKS_Meta_Value *,
            std::function<bool(const KKS_Meta *, const KKS_Meta *)> >;

    void attach(const string &file);

    const string &getKKSFileName();

    KKS_File_Head *getKKSFileHead();

    KKS_Meta *getMeta(u32_t id);

    std::set<u32_t> getAllKKS() const {
        return _all_kks;
    }

    /**
     * 根据开始时间和结束时间，返回包含数据的row
     * @param stime
     * @param etime
     */
    void getRowOffset(u32_t id, time_t stime, time_t etime, vector <off_t> &rows);

    vector <off_t> getRowOffset(u32_t id);

    bool getRowData(off_t offset, vector<char> &buffer);

    /**
     * 获取当前row的管理信息，注意:可能无效，不能长期持有,后续可以修改为share_ptr
     * @param id
     * @return 
     */
    KKS_Meta_Value *getMetaValue(u32_t id);

    /**
     * 添加KSS
     * @param id KSS id，>0
     * @param tm 时间值
     * @param vl 真实值
     */
    void addKKS(u32_t id, u32_t tm, u32_t vl, char quality);

    /**
     * 计算 delta_delat_tm
     * @param KKS_ROW_Head* head
     * @param KKS_Value* value
     * @return delta_delat_tm
     */
    static int getDeltaDelatValue(KKS_ROW_Head *head, KKS_Value *value);


    void test();

    //通过访问时间将row map控制在count个以内
    void reduceRowsByCount(u32_t count);

    void setMaxMemUsage(u64_t mem);

protected:

    void allocKKS(u32_t id, u32_t tm, u32_t vl, char quality);

    /**
     * 新分配row,并填充第一个数据
     * @param k  元数据
     * @param v  元数据管理
     * @param tm 时间值
     * @param vl 数据
     * @param delete_old_v
     * @return 
     */
    KKS_Meta_Value *
    allocRow(KKS_Meta *k, KKS_Meta_Value *v, u32_t tm, u32_t vl, char quality, bool delete_old_v = true);

    KKS_Meta_Value *lazyLoadRow(KKS_Meta *k, KKS_Meta_Value *v);

    void extendFileSize(off_t length);

private:
    KKS_File_Head *_head;
    KKS_Meta *_meta;
    int _fd = -1;
    off_t _file_length;
    u32_t _row_count = 0; //总体row数，用来控制使用内存
    u32_t _max_count = 0;
    std::set<uint32_t> _all_kks;
    kks_meta_set_type _kks_meta_sets{
            [](const KKS_Meta *k1, const KKS_Meta *k2) {
                return k1->kks_id < k2->kks_id;
            }};
    TL_Mmap _mmap;
    string _kfile_name;
};

#endif /* KKSFILE_H */

